<?php
/**
 * Основной класс меню.. содержит все основные методы по работы с меню
 *
 */

class Menu
{

	
	const 
		rootPointId = "1",
		pageMenuType = "1",
		alias_max_len = 100;
		
	/**
	 * Returns Main object
	 *
	 * @var MF_Main
	 */
	protected $_top;
	
	protected 
		$_parent,
		$menuPoints = array(),
		$menuClasses = array(),
		$menuStructure = array();

		
	private 
		$_flag_index = array('is_hidden' => 1);
		
		
	/**
	 * Root menu item pointer
	 *
	 * @var MenuItem
	 */
	public $rootPoint;
	public
		$menuTypes = array(
			//array("0","Undefined","Неопределен"),
			array("1","Page","Страница"),
			//array("2","Double","Дублирование пункта меню"),
			array("3","Url","Прямое указание на URL")
		);
		
		
	public function __construct(&$_top,&$_parent)
	{
		$this->_top =& $_top;
		$this->_parent =& $_parent;
		$this->rootPoint = $this->getPointById(self::rootPointId);
	}
	
	public function __get($param)
	{
		if ($param=="menuPoints") {
			return $this->menuPoints;
		} elseif ($param=="alias_max_len") {
			return self::alias_max_len;
		} else {		
			return false;
		}
	}
	
	/**
	 * Возвращает объект пункта меню, либо вызывает эксепшен если пункт не найден 
	 *
	 * @param Integer $menu_id Уникальный индефикатор пункта меню
	 * @return MenuItem
	 */
	public function getPointById($menu_id) {
		if (!isset($this->menuClasses[$menu_id])) {
			$get_point_data = $this->_top->_sql->exe("SELECT * FROM ".TAB_PREF."menu c WHERE menu_id='$menu_id'");
			if (count($get_point_data)==0) {
				throw $this->_top->Utils->PushError("Can not find menu item by ID $menu_id", "Документ не найден");
			}
			$this->menuPoints[$menu_id] =& $get_point_data[0];
			$this->menuClasses[$menu_id] = new MenuItem($this->_top,$this,$get_point_data[0]['menu_id']);
		}
		return $this->menuClasses[$menu_id];
	}
	
	
	public function getMenuBranchByRootId($root_menu_id) {
		
		if (!isset($this->menuStructure[$root_menu_id]))
			if (!$this->ConstructMenuBrabchCashe($root_menu_id)) {
				return false;
			}
		
		return $this->menuStructure[$root_menu_id];
	}
	
	/**
	 * Возвращает объект пункта меню, либо вызывает эксепшен если пункт не найден 
	 *
	 * @param String $alias Уникальный текстовый индефикатор пункта меню
	 * @return MenuItem
	 */
	public function getPointByAlias($alias) {
		$alias = $this->_top->Utils->prepareValueForSQL($alias);
		$find_point = $this->_top->_sql->exe("SELECT * FROM ".TAB_PREF."menu c WHERE alias='$alias'");
		if (count($find_point)==0) {
			throw $this->_top->Utils->PushError("Can not find menu item by alias $alias", "Документ не найден");
		}
		return $this->getPointById($find_point[0]['menu_id']);
	}
	
	private function ConstructMenuBrabchCashe($root_menu_id)
	{
		$this->menuStructure[$root_menu_id] = array();
		$points_list = $this->_top->_sql->exe("SELECT * FROM ".TAB_PREF."menu c WHERE root_menu_id=$root_menu_id ORDER BY sequence ASC");
		if (count($points_list)==0) return false;
		for ($l=0;$l<count($points_list);$l++) {
			array_push($this->menuStructure[$root_menu_id],$points_list[$l]['menu_id']);
			$this->menuPoints[$points_list[$l]['menu_id']] =& $points_list[$l];
			$this->menuClasses[$points_list[$l]['menu_id']] = new MenuItem($this->_top,$this,$points_list[$l]['menu_id']);
		}
		return true;
	}
	
	public function insertPoint($root_point_id) {
		$get_max_sequence = $this->_top->_sql->exe("SELECT IFNULL(MAX(sequence),0)+1 as s FROM ".TAB_PREF."menu WHERE root_menu_id=$root_point_id");
		$max_sequence = count($get_max_sequence)==0 ? "0" : $get_max_sequence[0]['s'];
		$this->_top->_sql->exeDummy("INSERT INTO ".TAB_PREF."menu(root_menu_id,sequence) VALUES($root_point_id,$max_sequence);");
		$new_menu_id = $this->_top->_sql->last_insert_id();
		
		$this->_top->Administrator->copyPrev($new_menu_id);
		$this->_top->AdminGroup->copyPrev($new_menu_id);
		
		if (isset($this->menuStructure[$root_point_id])) 
			array_push($this->menuStructure[$root_point_id],$new_menu_id);
		
		//return $this->menuClasses[$menu_id];
		return $this->getPointById($new_menu_id);
	}
	
	
	public function deletePoint($menu_id) {
		if ($this->menuClasses[$menu_id]->page!==false)
			$this->menuClasses[$menu_id]->page->delete();
			
		$get_sub_points = $this->getMenuBranchByRootId($menu_id);
		if ($get_sub_points)
			for ($p=0;$p<count($get_sub_points);$p++)
				$this->deletePoint($get_sub_points[$p]);
				
		
		$this->_top->_sql->exeDummy("DELETE FROM ".TAB_PREF."menu WHERE menu_id=$menu_id");
		
		$this->_top->Administrator->clearPrev($menu_id);
		$this->_top->AdminGroup->clearPrev($menu_id);
		
		$root_menu_id = $this->menuPoints[$menu_id]['root_menu_id'];
		
		
		$this->ConstructMenuBrabchCashe($this->menuPoints[$menu_id]['root_menu_id']);
		unset($this->menuClasses[$menu_id]);
		unset($this->menuPoints[$menu_id]);
		unset($this->menuStructure[$menu_id]);
		
		$this_branch = $this->getMenuBranchByRootId($root_menu_id);
		for ($b=0;$b<count($this_branch);$b++) {
			$menu_item = $this->getPointById($this_branch[$b]);
			$menu_item->sequence = ($b + 1);
		}
		return true;
	}
	
	public function updatePoint($param,$value,$menu_id) {
		if ($param=="root_point_id") return false;
		
		if ($param=="menu_type_id" && $value==self::pageMenuType && $this->getPointById($menu_id)->page==false) {
			$new_page = $this->_top->Page->add();
			$this->updatePoint("page_id",$new_page->page_id,$menu_id);
		}
		
		$this->menuPoints[$menu_id][$param] = $value;
		$value = $this->_top->Utils->prepareValueForSQL($value);
		$this->_top->_sql->exeDummy("UPDATE ".TAB_PREF."menu SET $param='$value' WHERE menu_id=$menu_id");
		return true;
	}
	
	public function setSequence($menu_id,$sequence) {
		$point_data = $this->getPointById($menu_id);
		$root_point_id = $point_data->root_menu_id;
		$branch = $this->getMenuBranchByRootId($root_point_id);
		
		$this->updatePoint("sequence",$sequence,$menu_id);
		$cur_idx = 1;
		for ($p=0;$p<count($branch);$p++) {
			if ($cur_idx==$sequence) $cur_idx++;
			if ($branch[$p]!=$menu_id) {
				$this->updatePoint("sequence",$cur_idx,$branch[$p]);
				$cur_idx++;
			}
		}
		
		$this->ConstructMenuBrabchCashe($root_point_id);
	}
	
	public function getFlagIndex($flag_name)
	{
		if (!isset($this->_flag_index[$flag_name])) throw $this->_top->Utils->PushError("No such user flag","Попытка обращения к несуществующему флагу пользователя");
		return $this->_flag_index[$flag_name];
	}
}



class MenuItem
{
	/**
	 * Содержит указатель на корневой класс Main
	 *
	 * @var Main
	 */
	private $_top;
	/**
	 * Содержит указатель на родительский класс Menu
	 *
	 * @var Menu
	 */
	private $_menuClassLink;
	/**
	 * Содержит значение id объекта меню
	 *
	 * @var Int
	 */
	private $_menu_id;
	
	/**
	 * Список запрещенных символов для использования в алиасе
	 *
	 * @var string
	 */
	private $_forbidden_alias_chars = "\/'@#$&*()!;,\"~`";
	
	
	#
	# Список переменных меню объявленных для совместимости с Zend Studio
	#
	
	/**
	 * Уникальный индентификатор пункта меню
	 *
	 * @var Integer
	 */
	public $menu_id;
	/**
	 * Уникальный текстовый индентификатор пункта меню
	 *
	 * @var String
	 */
	public $alias;
	/**
	 * Название пункта меню
	 *
	 * @var String
	 */
	public $name;
	/**
	 * Порядковый номер пункта меню
	 *
	 * @var Integer
	 */
	public $sequence;
	/**
	 * Количество подпунктов 
	 *
	 * @var Integer
	 */
	public $length;
	/**
	 * Указатель на родительский пункт
	 *
	 * @var SinglePage
	 */
	public $parent;
	/**
	 * Указатель на привязанную страницу
	 *
	 * @var SinglePage
	 */
	public $page;
	
	public function __construct(&$_top,&$_parent,$menu_id)
	{
		$this->_top = $_top;
		$this->_menuClassLink = $_parent;
		$this->_menu_id = $menu_id;
		
		unset($this->menu_id);
		unset($this->alias);
		unset($this->name);
		unset($this->sequence);
		unset($this->length);
		unset($this->parent);
		unset($this->page);
	}
	
	public function __set($param,$value)
	{
		if ($param=="root_point_id") {
			return false;
		} elseif ($param=="sequence") {
			return $this->_menuClassLink->setSequence($this->_menu_id,$value);
		} else {
			if ($param=="alias" && $value!="") {
				
				// Проверка алиаса на уникальность
				try {
					$check_alias = $this->_menuClassLink->getPointByAlias($value);
					$alias_exsists = true;
				} catch (PushError $e) {
					$alias_exsists = false;
				}
				
				if ($alias_exsists) {
					if ($check_alias->menu_id!=$this->_menu_id) {
						throw $this->_top->Utils->PushError("Trying to assign ununique alias for menu item", "Неуникальный алиас");
					}
				}
				
				// Проверка на наличие запрещенных симовлов
				if (!is_array($this->_forbidden_alias_chars)) {
					$this->_forbidden_alias_chars = str_split($this->_forbidden_alias_chars);
				}
				foreach ($this->_forbidden_alias_chars as $chr) {
					if (strpos($value,$chr) !== FALSE) {
						throw $this->_top->Utils->PushError("Trying to forbidden chars in menu alias", "Недопустимо использование служебных символов");
					}
				}
				
				// Проверка алиаса на достижение максимальной длинны
				$alias_max_len = $this->_menuClassLink->alias_max_len;
				if (strlen($value)>$alias_max_len) {
					throw $this->_top->Utils->PushError("Alias can not be longer when {$alias_max_len} chars", "Алиас не может превышать длинну {$alias_max_len} символов");
				}
			}
			return $this->_menuClassLink->updatePoint($param,$value,$this->_menu_id);
		}
	}
	
	public function __get($param)
	{
		if ($param=="length") {
			return $this->length();
		} elseif ($param=="flag") { 
			return new MenuFlags($this->_top,$this);
		} elseif ($param=="parent") return $this->_menuClassLink->getPointById($this->root_menu_id);
		elseif ($param=="page") return $this->page_id=='0' ? false : $this->_top->Page->getById($this->page_id);
		elseif (!isset($this->_menuClassLink->menuPoints[$this->_menu_id][$param])) return false;
		else return $this->_menuClassLink->menuPoints[$this->_menu_id][$param];
	}
	
	public function length() {
		$sub_points = $this->_menuClassLink->getMenuBranchByRootId($this->_menu_id);
		return !$sub_points ? 0 : count($sub_points);
	}
	
	
	public function item($idx)
	{
		$sub_points = $this->_menuClassLink->getMenuBranchByRootId($this->_menu_id);
		return $this->_menuClassLink->getPointById($sub_points[$idx]);
	}
	
	/**
	 * Создается и возвращается ссылка на синглтон класса SingleMenu
	 *
	 * @return MenuItem
	 */
	public function add()
	{
		return $this->_menuClassLink->insertPoint($this->_menu_id);
	}
	
	public function delete()
	{
		return $this->_menuClassLink->deletePoint($this->_menu_id);
	}
}
class MenuFlags
{
	private 
		$_top,
		$_parent;
		
	public function __construct($top,$parent)
	{
		$this->_top = $top;
		$this->_parent = $parent;
	}
	
	public function __get($param)
	{
		return $this->_top->BitMask->bitValue($this->_parent->properties,$this->_top->Menu->getFlagIndex($param));
	}
	
	public function __set($param,$value)
	{
		$flagIndex = $this->_top->Menu->getFlagIndex($param);
		
		if ($value==1) {
			$this->_parent->properties = $this->_top->BitMask->setOn($this->_parent->flags,$flagIndex);
		} elseif ($value==0) {
			$this->_parent->properties = $this->_top->BitMask->setOff($this->_parent->flags,$flagIndex);
		}
	}
	
}
